/*	--- 集成MyBatis --- 

使用 Hibernate/JPA 操作【数据库】时，【 这类ORM(Object Relational Mapping)干的主要工作】就是把 【ResultSet的每一行】 变成 【Java Bean】，或者【把 Java Bean】自动转换到 【INSERT/UPDATE语句 的参数】中，从而实现【ORM】。

而[ORM框架]之所以知道【如何把"行数据"映射到Java Bean】，是因为我们在【Java Bean的属性上】给了【足够的注解】作为【元数据】，【ORM框架】获取Java Bean的注解后，就知道如何进行双向映射。

------------------------------

那么，【ORM框架】是【如何跟踪 Java Bean 的修改】，以便【在update()操作中 更新 	【必要的属性】？

答案是【使用Proxy模式】，从【ORM框架】读取的【User实例】实际上【并不是 User类】，而是【User代理类】

【代理类】extends继承自【User类】，但针对【每个setter方法】做了【@Override 覆写】： */
public class UserProxy extends User {

	boolean _isNameChanged;

	public void setName(String name) {
		super.setName(name);
		_isNameChanged = true;
	}
}

/*
这样，【代理类】可以跟踪到【每个属性】的变化。

针对【一对多/多对一关系】时，[代理类]可以直接通过【getter方法】查询【数据库】： */
public class UserProxy extends User {

	Session _session;
	boolean _isNameChanged;

	public void setName(String name) {
		super.setName(name);
		_isNameChanged = true;
	}

	/*
		获取[User对象]关联的[Address对象]
	*/
	public Address getAddress() {
		Query q = _session.createQuery("from Address where userId =: userId");
		q.setParameter("userId", this.getId());

		List<Address> list = query.list();

		return list.isEmpty() ? null : list(0);
	}
}

/*
为了实现【这样的查询】，【UserProxy】必须【保存Hibernate的当前Session】。

但是，当【事务提交】后，【Session自动关闭】，此时再获取 getAddress() 将【无法访问数据库】，或者【获取的不是事务一致的数据】。

因此，【ORM框架】总是【引入了 Attached/Detached状态(连接/分离)】，表示【当前此Java Bean 到底是 在Session的范围内，还是脱离了Session变成了一个“游离”对象】。

很多初学者无法正确理解【状态变化】和【事务边界】，就会造成【大量的PersistentObjectException异常】。

------------------

这种【隐式状态】使得【普通Java Bean的生命周期】变得【复杂】。

此外，【Hibernate和JPA】为了实现【兼容多种数据库】，它使用【HQL或JPQL查询】，经过【一道转换】，变成【特定数据库的SQL】，理论上这样可以做到【无缝切换数据库】，但【这一层自动转换】除了【少许的性能开销外，给 "SQL级别的优化" 带来了麻烦】。

最后，【ORM框架】通常提供了【缓存】，并且还分为[一级缓存]和[二级缓存]。

[一级缓存]是指【在一个Session范围内的缓存】，【常见的情景】是【根据主键查询时】，【两次查询】可以【返回同一实例】： */
User user1 = session.load(User.class, 123);
User user2 = session.load(User.class, 123);


/*
【二级缓存】是指: 跨Session的缓存。(一般默认关闭，需要手动配置)。

【二级缓存】极大的【增加了数据的不一致性】，原因在于【SQL非常灵活】，常常【会导致意外的更新】。 例如： */

// 线程1读取
User user1 = session1.load(User.class, 123);
//...

// 一段时间后, 线程2读取
User user2 = session2.load(User.class, 123);


/*
当【二级缓存】生效的时候，【两个线程】读取的【User实例】是【一样的】，但是，【数据库对应的行记录】完全可能【被修改】，例如： 

	-- 给老用户增加100积分:
	UPDATE users SET bonus = bonus + 100 WHERE createdAt <= ?

【ORM】无法判断 id=123的用户 是否受 该UPDATE语句影响。

考虑到【数据库】通常会【支持多个应用程序】，此【UPDATE语句】可能由【其他进程】执行，【ORM框架】就更不知道了。

----------------

我们把【这种ORM框架】称之为【全自动ORM框架】。

对比【Spring提供的JdbcTemplate】，它和【ORM框架】相比，"JdbcTemplate"主要有【几点差别】：

	1. 查询后, 需要【手动提供 Mapper实例】以便【把 "ResultSet的每一行" 变为 "Java对象"】；

	2. 【增删改操作】所需的参数列表，需要【手动传入】，即： 把【User实例】变为[user.id, user.name, user.email]这样的列表，比较麻烦。

----------------------

但是【JdbcTemplate的优势】在于【它的确定性】：
	即【每次读取操作】一定是【数据库操作】而不是【缓存】，【所执行的SQL】是【完全确定的】; 

【JdbcTemplate缺点】就是：
	代码比较繁琐，构造INSERT INTO users VALUES (?,?,?)更是复杂。


所以，介于【全自动ORM, 如Hibernate】和【手写全部, 如JdbcTemplate】之间，还有一种【半自动的ORM】，它只负责把【ResultSet】自动映射到【Java Bean】，或者【自动填充Java Bean参数】，但仍需【自己写出SQL】。

【MyBatis】就是这样一种【半自动化ORM框架】。


===============================

我们来看看【如何在 Spring 中, 集成 MyBatis。】

首先，我们要【引入MyBatis本身】，其次，由于 Spring 并没有像 Hibernate那样内置 对MyBatis的集成

所以，我们需要再引入【MyBatis官方】自己开发的一个【与Spring集成的库】：

	· org.mybatis:mybatis:3.5.4
	· org.mybatis:mybatis-spring:2.0.4


和前面一样，【先创建DataSource】是【必不可少的】：  */
@Configuration 
@Component 
@EnableTransactionManagement 
@PropertySource("jdbc.properties")
public class AppConfig {
	//...

	@Bean 
	DataSource createDataSource() {
		//...
	}
}


/*
再回顾一下 Hibernate 和 JPA的SessionFactory 与 EntityManagerFactory

MyBatis 与之对应的是 SqlSessionFactory和SqlSession：

JDBC		Hibernate		JPA						MyBatis
---------------------------------------------------------------
DataSource	SessionFactory	EntityManagerFactory	SqlSessionFactory
Connection	Session			EntityManager			SqlSession
----------------------------------------------------------------------

可见，【ORM的设计套路】都是类似的。

【使用MyBatis的核心】就是【创建SqlSessionFactory】

这里我们需要创建的是【SqlSessionFactoryBean】： */
@Bean 
SqlSessionFactoryBean createSqlSessionFactoryBean(@Autowired DataSource dataSource) {
	var sqlSessionFactoryBean = new SqlSessionFactoryBean();
	sqlSessionFactoryBean.setDataSource(dataSource);

	return sqlSessionFactoryBean;
}


/*
因为 MyBatis 可以【直接使用Spring管理的"声明式事务"】

因此，【创建事务管理器】和【使用JDBC】是一样的： */
@Bean 
PlatformTransactionManager createTxManager(@Autowired DataSource dataSource) {
	return new DataSourceTransactionManager(dataSource);
}

/*
和 Hibernate 不同的是，MyBatis 使用 Mapper 来实现【映射】，而且【Mapper】必须是【接口】。

我们以【User类】为例，在【User类】和【users表】之间"映射"的【UserMapper】编写如下： */
public interface UserMapper {
	@Select("SELECT * FROM users WHERE id = #{id}")
	User getById(@Param("id") long id);
}


/*
注意：【这里的Mapper】不是【JdbcTemplate的RowMapper的概念】，它是【定义 访问users表 的 接口方法】。

比如我们定义了一个【User getById(long)的主键查询方法】，不仅要【定义 接口方法本身】，还要明确写出【查询的SQL】，这里用【注解@Select 标记】。

【SQL语句的任何参数】，都与【方法参数】按【名称】对应。

例如，【方法参数id的名字】通过【注解@Param()标记为id】，则【SQL语句】里将【来替换的占位符】就是"#{id}"。

如果有[多个参数]，那么【每个参数】命名后, 直接在SQL中写出【对应的占位符】即可： */
@Select("SELECT * FROM users LIMIT #{offset}, #{maxResults}")
List<User> getAll(@Param("offset") int offset, @Param("maxResults") int maxResults);

/*
注意：
	MyBatis执行[查询]后，将根据[方法的返回类型]自动把[ResultSet的每一行]转换为[User实例]，【转换规则】当然是【按"列名"和"属性名"对应】。

如果"列名"和"属性名"不同，最简单的方式是【编写SELECT语句的别名】：

	-- 列名是created_time，属性名是createdAt:
	SELECT id, name, email, created_time AS createdAt FROM users


执行【INSERT语句】就稍微【麻烦点】，因为我们希望【传入User实例】，因此，【定义的方法接口】与【@Insert注解】如下： */
@Insert("INSERT INTO users(email, password, name, createdAt) VALUES(#{user.email}, #{user.password}, #{user.name}, #{user.createdAt})")
void insert(@Param("user") User user);

/*
上述方法中, 传入的参数名称是[user]，[参数类型]是[User类]，在[SQL中引用]的时候，以 #{obj.property} 的方式【写占位符】。

和 Hibernate 这样的【全自动化ORM】相比，MyBatis 必须写出【完整的INSERT语句】。

----------------------

如果【users表】的【id】是【自增主键】

那么，我们在SQL中【不传入id】，但希望【获取 插入后的主键】，需要【再加一个 @Options注解】： */
@Option(useGeneratedKeys=ture, keyProperty="id", keyColumn="id")
@Insert("INSERT INTO users(email, password, name, createdAt) VALUES(#{user.email}, #{user.password}, #{user.name}, #{user.createdAt})")
void insert(@Param("user") User user);

/*
keyProperty 和 keyColumn 分别指出 "JavaBean的属性" 和 "数据库的主键列名"。

执行 [UPDATE和DELETE语句] 相对比较简单，我们定义方法如下： */
@Update("UPDATE users SET name = #{user.name}, createdAt = #{user.createdAt} WHERE id = #{user.id}")
void update(@Param("user") User user);

@Delete("DELETE FROM user WHERE id = #{id}")
void deletedById(@Param("id") long id);


/*
有了【UserMapper接口】，还需要【对应的实现类】才能真正执行【这些数据库操作的方法】。

虽然可以【自己写实现类】，但我们除了【编写UserMapper接口】外，还有【BookMapper、BonusMapper……】一个一个写太麻烦，

因此，【MyBatis】提供了一个【MapperFactoryBean】来自动创建【所有Mapper的实现类】。

可以用【一个简单的注解】来【启用它】: */
@MapperScan("com.itranswarp.learnjava.mapper")
//...其他注解
public class AppConfig {
	//...
}

/*
有了 @MapperScan

就可以让【MyBatis】自动扫描【指定包的"所有Mapper"】并创建【实现类】。

在【真正的业务逻辑】中，我们可以【直接注入】： */
@Component 
@Transactional 
public class UserService {
	// 注入UserMapper 
	@Autowired  // <-- 扫描Mapper
	UserMapper userMapper;

	public User getUserById(long id) {
		// 调用Mapper方法
		User user = userMapper.getById(id);

		if (user == null) {
			throw new RuntimeException("User not found by id");
		}

		return user;
	}
}

/*
可见，[业务逻辑]主要就是通过【XxxMapper定义的数据库方法】来【访问数据库】。


============================================


#	XML配置

上述在Spring中【集成MyBatis的方式】，我们只需要用到【注解】，并【没有任何XML配置文件】。

【MyBatis】也允许【使用XML】配置【映射关系】和【SQL语句】


例如，【更新User时】根据【属性值】构造【动态SQL】： 
---------------------------------------------------
<update id="updateUser">
  UPDATE users SET
  <set>
    <if test="user.name != null"> name = #{user.name} </if>
    <if test="user.hobby != null"> hobby = #{user.hobby} </if>
    <if test="user.summary != null"> summary = #{user.summary} </if>
  </set>
  WHERE id = #{user.id}
</update>
---------------------------------------------------


【编写XML配置】的优点是: 可以组装出【动态SQL】，并且把【所有SQL操作】集中在一起。

缺点是: 【配置起来太繁琐】，【调用方法时】如果想【查看SQL】还需要【定位到XML配置中】。

==================

这里我们不介绍XML的配置方式，需要了解的童鞋请自行阅读官方文档。

【使用MyBatis】最大的问题是: 【所有SQL】都需要【全部手写】

【优点】是【执行的SQL】就是【我们自己写的SQL】，【对SQL进行优化】非常简单，也可以【编写任意复杂的SQL】，或者【使用数据库的特定语法】，但【切换数据库可能就不太容易】。


好消息是【大部分项目】并没有【切换数据库的需求】，完全可以针对【某个数据库】编写【尽可能优化的SQL】。



===========================================================


#	练习

集成MyBatis  */


/*======================================================

#	----- 集成MyBatis の 小结 ----- 

1. MyBatis是一个半自动化的ORM框架，需要手写SQL语句，没有自动加载一对多或多对一关系的功能。


*/


